﻿#include <CreekGen.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>

#define randomize() srand((unsigned)time(NULL));
#define random(p) ((int)(rand()*p/RAND_MAX))

// --------------------------------- TItem -------------------------------------

/**
@brief Конструктор
*/
TItem::TItem() //+++
{
    return;
};

/**
@brief Деструктор
*/
TItem::~TItem() //+++
{
    return;
};

// --------------------------------- TVisible ----------------------------------

/**
@brief Конструктор
*/
TVisible::TVisible() //+++
{
    return;
};

/**
@brief Деструктор
*/
TVisible::~TVisible() //+++
{
    return;
};

/**
@brief Сдвинуть кадр вверх по ручью
*/
int TVisible::moveFrameUp(int delta) //+++
{
    return ER_OK;
};

/**
@brief Сдвинуть кадр вниз по ручью
*/
int TVisible::moveFrameDown(int delta) //+++
{
    return ER_OK;
};

// ---------------------------------- TCreek -----------------------------------

/**
@brief Конструктор
*/
TCreek::TCreek()
{
    /* Установка значений по умолчанию */
    screenWidth = 800;
    xLeftBegin = screenWidth/4;
    xRightBegin = screenWidth*3/4;
    creekLength = 10000;
    bankStep = screenWidth/20;
    minCreekWidth = screenWidth/4;
    maxCreekWidth = screenWidth*3/4;
    isCreekCyclic = false;
    delta = 5;
    frame.width = screenWidth;
    frame.height = 480;
    frame.up = frame.height;
    frame.down = 0;
    frame.isCreekCyclic = isCreekCyclic;
    /* Инициализация полей, описывающих предметы в ручье */
    itemsCountUnderWater = creekLength/500;
    itemsCountOnWater = itemsCountUnderWater;
    itemsCountOverWater = itemsCountUnderWater;
    /* Обнуление векотров с данными */
    leftBank.clear();
    leftPixelBank.clear();
    leftRectangleBank.clear();
    rightBank.clear();
    rightPixelBank.clear();
    rightRectangleBank.clear();
    return;
};

/**
@brief Деструктор
*/
TCreek::~TCreek() //+++
{
    /* Обнуление векотров с данными */
    leftBank.clear();
    leftPixelBank.clear();
    leftRectangleBank.clear();
    rightBank.clear();
    rightPixelBank.clear();
    rightRectangleBank.clear();
    return;
};

/**
@brief Создание каркасных точек, описывающих берега
*/
void TCreek::buildBankCurves()
{
    randomize();
    leftBank.clear();
    rightBank.clear();    
    TXYP readyPoint;

    /* Начальные значения координат */
    int y = 0;
    int xLeft = xLeftBegin;
    int xRight = xRightBegin;

    /* Цикл по всей длине ручья с шагом bankStep */
    for (int i = 0;i < creekLength-bankStep;i = i+bankStep)
    {
        /* Запоминание сгенерированной точки для левого берега */
        readyPoint.x = xLeft;
        readyPoint.y = y;
        leftBank.push_back(readyPoint);
        /* Запоминание сгенерированной точки для правого берега */
        readyPoint.x = xRight;
        rightBank.push_back(readyPoint);
        /* Переход выше по ручью */
        y = y+bankStep;
        if (y > creekLength) {y = creekLength;}
        /* Генерация новой точки левого берега*/
        /* Работа в цикле, пока не получим подходящую точку */
        while (true)
        {
            /* Генерация координаты новой точки */
            int tmpx = xLeft+(random(3)-1)*bankStep;
            /* Проверка сгенерированной координаты */
            if ((tmpx > bankStep)&&(tmpx < screenWidth/2))
            {
                /* В случае успеха - выход из цикла */
                xLeft = tmpx;
                break;
            }
        }
        /* Генерация новой точки правого берега*/
        /* Работа в цикле, пока не получим подходящую точку */
        while (true)
        {
            /* Генерация координаты новой точки */
            int add = (random(3)-1)*bankStep;
            int tmpx = xRight+add;
            /* Проверка сгенерированной координаты */
            if ((tmpx > (xLeft+minCreekWidth))&&(tmpx < (screenWidth-bankStep)))
            {
                /* Проверка ширины максимальной ширины ручья */
                if ((tmpx-xLeft <= maxCreekWidth)||(add < 0))
                {
                    /* В случае успеха - выход из цикла */
                    xRight = tmpx;
                    break;
                }
            }
        }
    }
    /* Конец цикла for по всей длине ручья */

    /* Генерация предпоследней точки ручья */
    /* Новая координата - среднее арифметическое между текущей координатой и начальной */
    xLeft = (xLeft+screenWidth/4)/2;
    xRight = (xRight+screenWidth*3/4)/2;
    /* Запоминание сгенерированной точки для левого берега */
    readyPoint.x = xLeft;
    readyPoint.y = y;
    leftBank.push_back(readyPoint);
    /* Запоминание сгенерированной точки для правого берега */
    readyPoint.x = xRight;
    rightBank.push_back(readyPoint);

    /* Генерация последней точки ручья */
    /* Запоминание сгенерированной точки для левого берега */
    readyPoint.x = xLeftBegin;
    readyPoint.y = creekLength;
    leftBank.push_back(readyPoint);
    /* Запоминание сгенерированной точки для правого берега */
    readyPoint.x = xRightBegin;
    rightBank.push_back(readyPoint);

    /* Вычисление производных в заданных точках */
    /* Производные для левого берега */
    leftBank.at(0).p = 0;
    leftBank.at(leftBank.size()-1).p = 0;
    unsigned int k;
    for (k = 1; k < leftBank.size()-1; k++) {
        if (((leftBank.at(k-1).y > leftBank.at(k).y)&&(leftBank.at(k+1).y > leftBank.at(k).y))||
            ((leftBank.at(k-1).y < leftBank.at(k).y)&&(leftBank.at(k+1).y < leftBank.at(k).y)))
        {
            leftBank.at(k).p = 0;
        }
        else
        {
            leftBank.at(k).p =((leftBank.at(k).x-leftBank.at(k-1).x)/(leftBank.at(k).y-leftBank.at(k-1).y)+
                               (leftBank.at(k+1).x-leftBank.at(k).x)/(leftBank.at(k+1).y-leftBank.at(k).y))*0.5;
        };
    }
    /* Производные для правого берега */
    rightBank.at(0).p = 0;
    rightBank.at(rightBank.size()-1).p = 0;
    for (k = 1; k < rightBank.size()-1; k++) {
        if (((rightBank.at(k-1).y > rightBank.at(k).y)&&(rightBank.at(k+1).y > rightBank.at(k).y))||
            ((rightBank.at(k-1).y < rightBank.at(k).y)&&(rightBank.at(k+1).y < rightBank.at(k).y)))
        {
            rightBank.at(k).p = 0;
        }
        else
        {
            rightBank.at(k).p =((rightBank.at(k).x-rightBank.at(k-1).x)/(rightBank.at(k).y-leftBank.at(k-1).y)+
                               (rightBank.at(k+1).x-rightBank.at(k).x)/(rightBank.at(k+1).y-leftBank.at(k).y))*0.5;
        };
    }

    return;
};

/**
@brief Попикселное построение берегов
@params from Номер точки из веторов leftBank или rightBank, с которой начинается построение
@params count Количество точек из веторов leftBank или rightBank для построения
@detailed В отличии от метода fillBankCurves(), данный метод воссоздает берег попиксельно
*/
void TCreek::fillBankCurves( int from, int count )
{
    /* Чистка старых данных */
    leftPixelBank.clear();
    rightPixelBank.clear();
    /* Проверка и корректировка входных параметров */
    if (from < 0) {from = 0;}
    if (count < 1) {count = 1;}
    /* Цикл по точкам берега */
    int to = from+count;
    for (int i = from; i <= to; i++)
    {
        /* Интерполяция интервала от точки (i-1) до точки i* (левый берег)/
        /* Корректировка индексов вектора с учетом цикличности */
        int jCur;
        if (i >= leftBank.size()) {
            jCur = i-leftBank.size();
        }
        else
        {
            jCur = i;
        }
        int jNext;
        if ((i+1) >= leftBank.size())
        {
            jNext = i-leftBank.size()+1;
        }
        else
        {
            jNext = i+1;
        }
        /* Текущая точка */
        int yCur = leftBank.at(jCur).x;
        int xCur = leftBank.at(jCur).y;
        /* Следующая точка */
        int yNext = leftBank.at(jNext).x;
        int xNext = leftBank.at(jNext).y;
        // Вычисление параметров интерполирующего сплайна
        int flag = 0;
        int yde = leftBank.at(jNext).p;
        int ydb = leftBank.at(jCur).p;
        int t = abs(xCur-xNext);
        int difYd_T = (yde-ydb)*t;
        float tmpC2 = yNext-yCur-0.5*t*(yde+ydb);
        float tmpC1 = sqrt(tmpC2*tmpC2+0.25*difYd_T*difYd_T);
        float tmpT = 0.5*t*t;
        float c1 = (-tmpC2-tmpC1)/tmpT;
        float c2 = (-tmpC2+tmpC1)/tmpT;
        float c;
        float a;
        float a1;
        float a2;
        if ((c1 == 0)&&(c2 == 0))
        {
            c = 0;
            a = 0;
        }
        else
        {
            a1 = -1;
            a2 = -1;
            if (c1 != 0) {a1 = (yde-ydb+t*c1)/(2*c1);}
            if (c2 != 0) {a2 = (yde-ydb+t*c2)/(2*c2);}
            if ((a1 <= t)&&(a1 >= 0))
            {
                flag = flag|1;
                c = c1;
                a = a1;
            }
            if ((a2 <= t)&&(a2 >= 0))
            {
                flag = flag|2;
                c = c2;
                a = a2;
            }
            if (flag == 3)
            {
                if (fabs(c2) < fabs(c1))
                {
                    c = c2;
                    a = a2;
                }
                else
                {
                    c = c1;
                    a = a1;
                }
            }
        }
        /*Цикл интерполяции интервала*/
        int x = 0;
        int res;
        while (true)
        {
            if (x <= (t-a))
            {
                res = yCur+ydb*x-x*x*c/2;
            }
            else
            {
                float temp = t-x;
                float resD = yde-c*temp;
                res = yNext-resD*temp-temp*temp*c/2;
            }
            TXY xy;
            xy.y = x+xCur;
            xy.x = res+(random(5)-2)*delta/4;
            if (leftPixelBank.size() > 0) {
                if (!((leftPixelBank.at(leftPixelBank.size()-1).x == xy.x)&&  //+++
                    (leftPixelBank.at(leftPixelBank.size()-1).y == xy.y))) {
                    if (leftPixelBank.at(leftPixelBank.size()-1).y < xy.y) {
                        leftPixelBank.push_back(xy);
                    }
                }
            }
            else
            {
                leftPixelBank.push_back(xy);
            }
            x = x+delta;
            if (x > (xNext-xCur)) {break;}
        }

        /* Интерполяция интервала от точки (i-1) до точки i* (правый берег)/
        /* Корректировка индексов вектора с учетом цикличности */
        if (i >= rightBank.size()) {
            jCur = i-rightBank.size();
        }
        else
        {
            jCur = i;
        }
        if ((i+1) >= rightBank.size())
        {
            jNext = i-rightBank.size()+1;
        }
        else
        {
            jNext = i+1;
        }
        /* Текущая точка */
        yCur = rightBank.at(jCur).x;
        xCur = rightBank.at(jCur).y;
        /* Следующая точка */
        yNext = rightBank.at(jNext).x;
        xNext = rightBank.at(jNext).y;
        /* Вычисление параметров интерполирующего сплайна */
        flag = 0;
        yde = rightBank.at(jNext).p;
        ydb = rightBank.at(jCur).p;
        t = abs(xCur-xNext);
        difYd_T = (yde-ydb)*t;
        tmpC2 = yNext-yCur-0.5*t*(yde+ydb);
        tmpC1 = sqrt(tmpC2*tmpC2+0.25*difYd_T*difYd_T);
        tmpT = 0.5*t*t;
        c1 = (-tmpC2-tmpC1)/tmpT;
        c2 = (-tmpC2+tmpC1)/tmpT;
        if ((c1 == 0)&&(c2 == 0))
        {
            c = 0;
            a = 0;
        }
        else
        {
            a1 = -1;
            a2 = -1;
            if (c1 != 0) {a1 = (yde-ydb+t*c1)/(2*c1);}
            if (c2 != 0) {a2 = (yde-ydb+t*c2)/(2*c2);}
            if ((a1 <= t)&&(a1 >= 0))
            {
                flag = flag|1;
                c = c1;
                a = a1;
            }
            if ((a2 <= t)&&(a2 >= 0))
            {
                flag = flag|2;
                c = c2;
                a = a2;
            }
            if (flag == 3)
            {
                if (fabs(c2) < fabs(c1))
                {
                    c = c2;
                    a = a2;
                }
                else
                {
                    c = c1;
                    a = a1;
                }
            }
        }
        /*Цикл интерполяции интервала*/
        x = 0;
        while (true)
        {
            if (x <= (t-a))
            {
                res = yCur+ydb*x-x*x*c/2;
            }
            else
            {
                float temp = t-x;
                float resD = yde-c*temp;
                res = yNext-resD*temp-temp*temp*c/2;
            }
            TXY xy;
            xy.y = x+xCur;
            xy.x = res+(random(5)-2)*delta/4;
            if (rightPixelBank.size() > 0) {
                if (!((rightPixelBank.at(rightPixelBank.size()-1).x == xy.x)&&  //+++
                    (rightPixelBank.at(rightPixelBank.size()-1).y == xy.y))) {
                    if (rightPixelBank.at(rightPixelBank.size()-1).y < xy.y) {
                       rightPixelBank.push_back(xy);
                    }
                }
            }
            else
            {
                rightPixelBank.push_back(xy);
            }
            x = x+delta;
            if (x > (xNext-xCur)) {break;}
        }
    }
    /* Конец for-цикла по точкам берега */

    /* Заполнение векторов прямоугольников, ограничивающих берега */
    leftRectangleBank.clear();
    rightRectangleBank.clear();
    for (int i = 0; i < leftPixelBank.size()-1; i++) {
        TRectangle tmpRectangle;
        /* Прямоугольник для левого берега */
        tmpRectangle.v1.x = leftPixelBank.at(i).x;
        tmpRectangle.v1.y = leftPixelBank.at(i).y;
        tmpRectangle.v2.x = leftPixelBank.at(i+1).x;
        tmpRectangle.v2.y = leftPixelBank.at(i+1).y;
        tmpRectangle.v3.x = 0;
        tmpRectangle.v3.y = leftPixelBank.at(i+1).y;
        tmpRectangle.v4.x = 0;
        tmpRectangle.v4.y = leftPixelBank.at(i).y;
        tmpRectangle.c.x = ((tmpRectangle.v2.x-tmpRectangle.v3.x)+(tmpRectangle.v1.x-tmpRectangle.v4.x))/2;
        tmpRectangle.c.y = (tmpRectangle.v2.y-tmpRectangle.v1.y)/2;
        leftRectangleBank.push_back(tmpRectangle);
        /* Прямоугольник для левого берега */
        tmpRectangle.v1.x = rightPixelBank.at(i).x;
        tmpRectangle.v1.y = rightPixelBank.at(i).y;
        tmpRectangle.v2.x = rightPixelBank.at(i+1).x;
        tmpRectangle.v2.y = rightPixelBank.at(i+1).y;
        tmpRectangle.v3.x = creekLength;
        tmpRectangle.v3.y = rightPixelBank.at(i+1).y;
        tmpRectangle.v4.x = creekLength;
        tmpRectangle.v4.y = rightPixelBank.at(i).y;
        tmpRectangle.c.x = ((tmpRectangle.v3.x-tmpRectangle.v2.x)+(tmpRectangle.v4.x-tmpRectangle.v1.x))/2;
        tmpRectangle.c.y = (tmpRectangle.v2.y-tmpRectangle.v1.y)/2;
        rightRectangleBank.push_back(tmpRectangle);
    }

    /* Конец цикла построения прямоугольников */
    return;
};

/**
@brief Получить ширину ручья по указанной координате
*/
int TCreek::getCreekWidth( int y )
{
    /* Проверка входных параметров */
    if (y>creekLength) {y = creekLength;}
    int res = 0;
    /* Цикл поиска ближайших точек каркаса ручья */
    for (int i = 0; i < leftBank.size(); i++) {
        if (leftBank.at(i).y>y)
        {
            /* Вычисление ширины интерполяцией ширин между ближайшими точками каркаса */
            i--;
            float l = y-leftBank.at(i).y;
            float b = rightBank.at(i).x - leftBank.at(i).x;
            float a;
            float L;
            if (i<leftBank.size()-1)
            {
                L = leftBank.at(i+1).y - leftBank.at(i).y;
                a = rightBank.at(i+1).x - leftBank.at(i+1).x;
            }
            else
            {
                L = leftBank.at(0).y - leftBank.at(i).y;
                a = rightBank.at(0).x - leftBank.at(i).x;
            };
            res = b+(a-b)*(l/L);
            break;
        }
    }

    return res;
}

/**
@brief Получить положение левого берега по указанной координате
*/
int TCreek::getLeftBank( int y )
{
    /* Проверка входных параметров */
    if (y>creekLength) {y = creekLength;}
    int res = 0;
    /* Цикл поиска ближайших точек берега ручья */
    for (int i = 0; i < leftPixelBank.size(); i++) {
        if (leftPixelBank.at(i).y>y)
        {
            i--;
            res = leftPixelBank.at(i).x;
            break;
        }
    }

    return res;
}

/**
@brief Получить положение правого берега по указанной координате
*/
int TCreek::getRightBank( int y )
{
    /* Проверка входных параметров */
    if (y>creekLength) {y = creekLength;}
    int res = 0;
    /* Цикл поиска ближайших точек берега ручья */
    for (int i = 0; i < rightPixelBank.size(); i++) {
        if (rightPixelBank.at(i).y>y)
        {
            i--;
            res = rightPixelBank.at(i).x;
            break;
        }
    }

    return res;
}

/**
@brief Начальное расположение предметов в ручье
*/
void TCreek::placeItems() //+++
{
    randomize();
    /* Чистка векторов с описанием предметов под/на/над/ водой */
    itemsUnderWater.clear();
    itemsOnWater.clear();
    itemsOverWater.clear();
    /* Подводные предметы */
    int density = creekLength/itemsCountUnderWater;
    TItem item;
    for (int i = 0; i < itemsCountUnderWater; i++) {
      item.y = i*density+random(density);
      item.x = getLeftBank( item.y )+random(getCreekWidth( item.y ));
      int max = IT_MAX;
      item.type = random(max);
      item.isMoveable = false;
      /* Изначально все предметы невидимы (нужно вызывать метод checkVisibleItems)*/
      item.isVisible = false;
      item.speed = 0;
      item.picture = 255; // +++
      item.width = random(5*delta)+1; // +++
      item.height = random(5*delta)+1; //+++
      itemsUnderWater.push_back(item);
    }
    /* Водные предметы */
    density = creekLength/itemsCountOnWater;
    for (int i = 0; i < itemsCountOnWater; i++) {
      item.y = i*density+random(density);
      item.x = getLeftBank( item.y )+random(getCreekWidth( item.y ));
      int max = IT_MAX;
      item.type = random(max);
      item.isMoveable = true;
      /* Изначально все предметы невидимы (нужно вызывать метод checkVisibleItems)*/
      item.isVisible = false;
      item.speed = 0;
      item.picture = 256*65280; // +++
      item.width = random(5*delta)+1; // +++
      item.height = random(5*delta)+1; //+++
      itemsOnWater.push_back(item);
    }
    /* Надводные предметы */
    density = creekLength/itemsCountOverWater;
    for (int i = 0; i < itemsCountOverWater; i++) {
      item.y = i*density+random(density);
      item.x = getLeftBank( item.y )+random(getCreekWidth( item.y ));
      int max = IT_MAX;
      item.type = random(max);
      item.isMoveable = false;
      /* Изначально все предметы невидимы (нужно вызывать метод checkVisibleItems)*/
      item.isVisible = false;
      item.speed = 0;
      item.picture = 65280; // +++
      item.width = random(5*delta)+1; // +++
      item.height = random(5*delta)+1; //+++
      itemsOverWater.push_back(item);
    }

    return;
};

/**
@brief Проверка видимости предметов в ручье
@detailed Производится обход всех предметов. Предметы, которые не находятся в кадре помечаются как "невидимые"
*/
void TCreek::checkVisibleItems() //+++
{
    return;
};

/**
@brief Процедура генерации ручья
@detailed Вызывает методы fillBankCurves() и    placeItems(), создавая берега ручья и располагая в нем предметы
*/
void TCreek::generateCreek() //+++
{
    buildBankCurves();
    placeItems();
    return;
};
